/*
 * To change this template, choose Tools | Templates
 * and open the template in the editor.
 */

package com.jellyfishumbrella.GL;

import static org.lwjgl.opengl.GL11.GL_BLEND;
import static org.lwjgl.opengl.GL11.GL_COLOR_BUFFER_BIT;
import static org.lwjgl.opengl.GL11.GL_CULL_FACE;
import static org.lwjgl.opengl.GL11.GL_DEPTH_BUFFER_BIT;
import static org.lwjgl.opengl.GL11.GL_DEPTH_COMPONENT;
import static org.lwjgl.opengl.GL11.GL_DEPTH_TEST;
import static org.lwjgl.opengl.GL11.GL_FLOAT;
import static org.lwjgl.opengl.GL11.GL_LEQUAL;
import static org.lwjgl.opengl.GL11.GL_LIGHTING;
import static org.lwjgl.opengl.GL11.GL_LINES;
import static org.lwjgl.opengl.GL11.GL_LINE_LOOP;
import static org.lwjgl.opengl.GL11.GL_LINE_STRIP;
import static org.lwjgl.opengl.GL11.GL_MODELVIEW;
import static org.lwjgl.opengl.GL11.GL_MODELVIEW_MATRIX;
import static org.lwjgl.opengl.GL11.GL_NICEST;
import static org.lwjgl.opengl.GL11.GL_NORMALIZE;
import static org.lwjgl.opengl.GL11.GL_ONE_MINUS_SRC_ALPHA;
import static org.lwjgl.opengl.GL11.GL_PERSPECTIVE_CORRECTION_HINT;
import static org.lwjgl.opengl.GL11.GL_POINTS;
import static org.lwjgl.opengl.GL11.GL_POLYGON;
import static org.lwjgl.opengl.GL11.GL_PROJECTION;
import static org.lwjgl.opengl.GL11.GL_PROJECTION_MATRIX;
import static org.lwjgl.opengl.GL11.GL_QUADS;
import static org.lwjgl.opengl.GL11.GL_QUAD_STRIP;
import static org.lwjgl.opengl.GL11.GL_SMOOTH;
import static org.lwjgl.opengl.GL11.GL_SRC_ALPHA;
import static org.lwjgl.opengl.GL11.GL_TRIANGLES;
import static org.lwjgl.opengl.GL11.GL_TRIANGLE_FAN;
import static org.lwjgl.opengl.GL11.GL_TRIANGLE_STRIP;
import static org.lwjgl.opengl.GL11.GL_VIEWPORT;
import static org.lwjgl.opengl.GL11.glBegin;
import static org.lwjgl.opengl.GL11.glBlendFunc;
import static org.lwjgl.opengl.GL11.glClear;
import static org.lwjgl.opengl.GL11.glClearColor;
import static org.lwjgl.opengl.GL11.glClearDepth;
import static org.lwjgl.opengl.GL11.glColor3f;
import static org.lwjgl.opengl.GL11.glDepthFunc;
import static org.lwjgl.opengl.GL11.glDisable;
import static org.lwjgl.opengl.GL11.glEnable;
import static org.lwjgl.opengl.GL11.glEnd;
import static org.lwjgl.opengl.GL11.glGetFloat;
import static org.lwjgl.opengl.GL11.glGetInteger;
import static org.lwjgl.opengl.GL11.glHint;
import static org.lwjgl.opengl.GL11.glLineWidth;
import static org.lwjgl.opengl.GL11.glLoadIdentity;
import static org.lwjgl.opengl.GL11.glMatrixMode;
import static org.lwjgl.opengl.GL11.glPointSize;
import static org.lwjgl.opengl.GL11.glPopMatrix;
import static org.lwjgl.opengl.GL11.glPushMatrix;
import static org.lwjgl.opengl.GL11.glReadPixels;
import static org.lwjgl.opengl.GL11.glShadeModel;
import static org.lwjgl.opengl.GL11.glTranslatef;
import static org.lwjgl.opengl.GL11.glVertex3f;
import static org.lwjgl.opengl.GL11.glViewport;
import static org.lwjgl.opengl.GL12.GL_MAX_3D_TEXTURE_SIZE;
import static org.lwjgl.opengl.GL20.GL_MAX_COMBINED_TEXTURE_IMAGE_UNITS;
import static org.lwjgl.util.glu.GLU.gluPerspective;
import static org.lwjgl.util.glu.GLU.gluUnProject;

import java.nio.FloatBuffer;
import java.nio.IntBuffer;
import java.util.LinkedHashMap;
import java.util.LinkedList;
import java.util.Map;
import java.util.concurrent.LinkedBlockingQueue;

import org.lwjgl.BufferUtils;
import org.lwjgl.LWJGLException;
import org.lwjgl.input.Keyboard;
import org.lwjgl.input.Mouse;
import org.lwjgl.opengl.Display;
import org.lwjgl.opengl.DisplayMode;
import org.lwjgl.util.vector.Matrix4f;
import org.lwjgl.util.vector.Vector3f;

/**
 * @author 500441547
 */
public class RendererGL {
	public static final String WINDOW_TITLE = "TOTEM";
	public static final int DISPLAY_WIDTH = 1920 / 2;
	public static final int DISPLAY_HEIGHT = 1080 / 2;

	public static final int RENDERSTYLE_COMPILED = 0;
	public static final int RENDERSTYLE_VBO = 1;
	public static int renderstyle = RENDERSTYLE_COMPILED;

	public static final int SEED = (int) (32767 * Math.random());

	public static AbstractGameLogic gameLogic;

	public static Camera camera;
	public static Hud hud;
	public static Lighting lighting;
	public static Sun sun;
	public static MyFog fog;

	public static TextureHash textureHash = new TextureHash();
	public static LinkedHashMap<String, Surface> surfaceHash = new LinkedHashMap<String, Surface>();

	public static Matrix4f projectionMatrix;
	public static Matrix4f identityMatrix;

	public float GRAVITY = -0.1f;

	public boolean doUpdate = false;

	public static Map<Integer, Selectable> selectables;
	public IntBuffer selectionBuffer = BufferUtils.createIntBuffer(512);

	public static LinkedBlockingQueue<Renderable> renderables;
	
	public static LinkedList<Integer> glModeQ = new LinkedList<Integer>();

	public RendererGL() throws LWJGLException {
		System.out.println("making engine: " + SEED);
		setupGLModes();

		createDisplay();
	}
	
	public static Surface addSurface(String surfaceName){
		if(surfaceHash.containsKey(surfaceName)){
			return surfaceHash.get(surfaceName);
		}else{
			Surface newSurface = new Surface(surfaceName);
			surfaceHash.put(surfaceName, newSurface);
			return newSurface;
		}
	}
	
	public void setupGLModes(){
		glModeQ.add(GL_POINTS);
		glModeQ.add(GL_LINES);
		glModeQ.add(GL_LINE_STRIP);
		glModeQ.add(GL_LINE_LOOP);
		glModeQ.add(GL_TRIANGLES);
		glModeQ.add(GL_TRIANGLE_STRIP);
		glModeQ.add(GL_TRIANGLE_FAN);
		glModeQ.add(GL_QUADS);
		glModeQ.add(GL_QUAD_STRIP);
		glModeQ.add(GL_POLYGON);
	}

	public void add(Renderable meshToAdd) {
		renderables.add(meshToAdd);
	}

	public void remove(Renderable meshToRemove) {
		renderables.remove(meshToRemove);
	}

	public boolean contains(Renderable meshToCheck) {
		return renderables.contains(meshToCheck);
	}

	public void connectToLogic(AbstractGameLogic gameLogic) throws LWJGLException {
		this.gameLogic = gameLogic;
		gameLogic.renderGL = this;

		initGL();
		resizeGL();
		renderables = new LinkedBlockingQueue<Renderable>();

		create();

		// gameLogic.createGrid();
	}

	public void createDisplay() throws LWJGLException {
		// Display
		Display.setDisplayMode(new DisplayMode(DISPLAY_WIDTH, DISPLAY_HEIGHT));
		Display.setFullscreen(false);
		// Display.setVSyncEnabled(true);
		Display.setTitle(WINDOW_TITLE);
		Display.create();
	}

	public void initGL() {
		projectionMatrix = new Matrix4f();
		identityMatrix = new Matrix4f();

		glClearColor(0.0f, 0.25f, 0.5f, 1.0f);
		glClearDepth(1.0f);
		glShadeModel(GL_SMOOTH);
		glEnable(GL_BLEND);
		glBlendFunc(GL_SRC_ALPHA, GL_ONE_MINUS_SRC_ALPHA);
		glDepthFunc(GL_LEQUAL);
		glHint(GL_PERSPECTIVE_CORRECTION_HINT, GL_NICEST);
		glEnable(GL_CULL_FACE);
		glEnable(GL_NORMALIZE);
		GL_Enables();

		System.out.println("max texture size = " + GL_MAX_3D_TEXTURE_SIZE);
		System.out.println("max num of textures = " + GL_MAX_COMBINED_TEXTURE_IMAGE_UNITS);
	}

	public static void GL_Enables() {
		glEnable(GL_LIGHTING);
		glEnable(GL_DEPTH_TEST);
	}

	public static void GL_Disables() {
		glDisable(GL_LIGHTING);
		glDisable(GL_DEPTH_TEST);
	}

	public void resizeGL() {
		glViewport(0, 0, DISPLAY_WIDTH, DISPLAY_HEIGHT);
		zoom();
	}

	public void zoom() {
		glMatrixMode(GL_PROJECTION);
		glLoadIdentity();

		gluPerspective(camera.FOV, (float) DISPLAY_WIDTH / (float) DISPLAY_HEIGHT, 0.01f, 1000000.0f);

		glPushMatrix();

		glMatrixMode(GL_MODELVIEW);
		glLoadIdentity();
		glPushMatrix();
	}

	public void render() {
		if (doUpdate) {
			zoom();
			doUpdate = false;
		}
		glClear(GL_COLOR_BUFFER_BIT | GL_DEPTH_BUFFER_BIT);
		glMatrixMode(GL_MODELVIEW);
		glLoadIdentity();

		camera.render();

		sun.render();

		glLineWidth(0.5f);
		glPointSize(3);
		for (Renderable currRenderable : renderables) {
			currRenderable.render();
		}

		// gameLogic.octree.render();

		hud.render();
	}
	
	public void testRender() {
		glPushMatrix();
		glTranslatef(0, 0, 0);
		glColor3f(0.5f, 0.5f, 1.0f); // Set The Color To Blue One Time Only
		glBegin(GL_TRIANGLES); // Drawing Using Triangles
		glColor3f(1.0f, 0.0f, 0.0f); // Set The Color To Red
		glVertex3f(0.0f, 100.0f, 0.0f); // Top
		glColor3f(0.0f, 1.0f, 0.0f); // Set The Color To Green
		glVertex3f(-100.0f, -100.0f, 0.0f); // Bottom Left
		glColor3f(0.0f, 0.0f, 100.0f); // Set The Color To Blue
		glVertex3f(100.0f, -100.0f, 0.0f); // Bottom Right
		glEnd(); // Finished Drawing The Triangle
		glTranslatef(300.0f, 0.0f, 0.0f); // Move Right 3 Units
		glColor3f(0.5f, 0.5f, 1.0f); // Set The Color To Blue One Time Only
		glBegin(GL_QUADS); // Draw A Quad
		glVertex3f(-100.0f, 100.0f, 0.0f); // Top Left
		glVertex3f(100.0f, 100.0f, 0.0f); // Top Right
		glVertex3f(100.0f, -100.0f, 0.0f); // Bottom Right
		glVertex3f(-100.0f, -100.0f, 0.0f); // Bottom Left
		glEnd(); // Done Drawing The Quad
		glPopMatrix();
	}

	public void create() throws LWJGLException {
		this.camera = new Camera();

		// Lighting.addDistant(new Vector3f(0.0f, 0.0f, 2.0f), new float[] {
		// 1.0f, 1.0f, 1.0f, 1.0f }, new float[] { 1.0f, 1.0f, 1.0f, 1.0f }, new
		// float[] { 1.0f, 1.0f, 1.0f, 1.0f });
		// Lighting.addPoint(new Vector3f(1000.0f, 1000.0f, 0.0f), new float[] {
		// 1.0f, 1.0f, 1.0f, 1.0f }, new float[] { 1.0f, 1.0f, 1.0f, 1.0f }, new
		// float[] { 1.0f, 1.0f, 1.0f, 1.0f });

		sun = new Sun();
		sun.addSun(new float[] { 1.0f, 1.0f, 1.0f, 1.0f }, new float[] { 1.0f, 1.0f, 1.0f, 1.0f }, new float[] { 1.0f, 1.0f, 1.0f, 1.0f });

		// fog = new MyFog();

		hud = new Hud();

		// Keyboard
		Keyboard.create();

		// Mouse
		Mouse.setGrabbed(true);
		Mouse.create();

	}

	public void destroy() {
		// Methods already check if created before destroying.
		Mouse.destroy();
		Keyboard.destroy();
		Display.destroy();
	}

	/**
	 * The selection magic happens here.
	 * 
	 * @param mouse_x
	 * @param mouse_y
	 */
	public static Vector3f screenTo3D(int screenX, int screenY) {
		IntBuffer viewport = BufferUtils.createIntBuffer(16);
		FloatBuffer modelview = BufferUtils.createFloatBuffer(16);
		FloatBuffer projection = BufferUtils.createFloatBuffer(16);
		FloatBuffer winZ = BufferUtils.createFloatBuffer(1);
		float winX, winY;
		FloatBuffer position = BufferUtils.createFloatBuffer(3);

		glGetFloat(GL_MODELVIEW_MATRIX, modelview);
		glGetFloat(GL_PROJECTION_MATRIX, projection);
		glGetInteger(GL_VIEWPORT, viewport);

		winX = (float) screenX;
		winY = (float) viewport.get(3) - (float) screenY;

		// glReadPixels(screenX, (int)winY, 1, 1, GL_DEPTH_COMPONENT, GL_FLOAT,
		// winZ);
		glReadPixels(screenX, screenY, 1, 1, GL_DEPTH_COMPONENT, GL_FLOAT, winZ);

		gluUnProject(winX, winY, winZ.get(), modelview, projection, viewport, position);
		Vector3f wPos = new Vector3f(position.get(0), position.get(1), position.get(2));
		return wPos;
	}
}
